package com.sdata.customize.service;

import cn.hutool.core.collection.CollUtil;
import com.sdata.asset.service.dataset.DatasetUtils;
import com.sdata.core.text.StringUtils;
import com.sdata.customize.pojo.ApplyParams;
import com.sdata.flow.v2.dao.FlowInstanceApprovalLogDaoV2;
import com.sdata.flow.v2.interaction.adaptation.FormAdaptationV2;
import com.sdata.flow.v2.service.FlowComponentItemServiceV2;
import com.sdata.flow.v2.service.FlowDefinitionServiceV2;
import com.sdata.flow.v2.service.FlowInstanceServiceV2;
import com.sdata.flow.v2.service.FlowInstanceTaskServiceV2;
import com.sdata.flow.v2.service.activiti.ActivitiFlowServiceImplV2;
import com.sdata.flow.v2.utils.FlowELUtil;
import com.sdata.flow.v2.utils.FlowUtilV2;
import com.sdata.foundation.core.util.SDException;
import com.sdata.foundation.web.util.CookieUtils;
import com.sdata.meta.commons.Column;
import com.sdata.meta.commons.MetadataConstants;
import com.sdata.meta.commons.sys.UserItem;
import com.sdata.meta.flow.conf.FlowConstants;
import com.sdata.meta.flow.pojo.*;
import com.sdata.meta.form.constants.ComponentConstants;
import com.sdata.starter.utils.ThreadLocalCache;
import com.sdata.system.service.user.UserItemService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.sdata.meta.form.dto.base.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AnJiFlowApplyCustomizeService {

    @Autowired
    private FlowDefinitionServiceV2 flowDefinitionServiceV2;

    @Autowired
    private FormAdaptationV2 formAdaptationV2;

    @Autowired
    private FlowInstanceServiceV2 flowInstanceServiceV2;

    @Autowired
    private UserItemService userItemService;

    @Autowired
    private FlowInstanceTaskServiceV2 flowInstanceTaskServiceV2;

    @Autowired
    private ActivitiFlowServiceImplV2 activitiFlowService;

    @Autowired
    private FlowComponentItemServiceV2 flowComponentItemService;

    @Autowired
    private FlowInstanceApprovalLogDaoV2 flowInstanceApprovalLogDaoV2;
    public void apply(ApplyParams applyParam) {
        String formId = applyParam.getFormId();
        String dataId = applyParam.getDataId();
        String userId = applyParam.getUserId();

        // 根据表单ID查询业务流定义
        FlowDefinition flowDefinitionInDb = flowDefinitionServiceV2.queryByFormId(formId);

        // 检查表单是否绑定了业务流
        if (null == flowDefinitionInDb) {
            log.error("当前表单没有绑定业务流:{}", formId);
            throw new SDException("当前表单没有绑定业务流");
        }

        FlowDefinition flowDefinition = flowDefinitionServiceV2.queryByFlowId(flowDefinitionInDb.getId());
        // 判断业务流是否启用
        if (MetadataConstants.Bool.NO == flowDefinition.getActive_flag()) {
            log.error("当前业务流未启用:{}", flowDefinition.getId());
            throw new SDException("当前业务流未启用");
        }

        // 检查业务流中使用的系统组件资产是否有效或存在
        formAdaptationV2.checkFlowSystemComponentAsset(flowDefinition);

        UserItem userItem = userItemService.getById(userId);
        // 获取业务流定义ID
        String flowId = flowDefinition.getId();

        // 先查询当前数据是否已经发起流程
        FlowInstance flowInstance = flowInstanceServiceV2.queryByContentId(dataId);
        if (flowInstance != null) {
            throw new SDException("该条数据已经发起过业务流了，不可重复发起(待发起也算)！");
        }

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        // 将 request 对象存储到 ThreadLocal 缓存中，以便后续方法（如 buildFlowInstance）可以访问
        ThreadLocalCache.set("request", request);
        // 调用内部方法构建并启动业务流实例
        this.buildFlowInstance(dataId, flowId, formId, flowDefinition, userItem);
    }


    /**
     * 构建并初始化业务流实例
     * 创建 FlowInstance 记录，初始化业务流上下文，并启动 Activiti 流程
     *
     * @param dataId         表单数据ID
     * @param flowId         业务流定义ID
     * @param formId         表单ID
     * @param flowDefinition 业务流定义对象
     * @param userItem       发起业务流的用户信息
     */
    private void buildFlowInstance(String dataId, String flowId, String formId, FlowDefinition flowDefinition, UserItem userItem) {
        // 创建一个 Map 用于存储需要更新到表单内容的数据
        Map<String, Object> formContentData = new HashMap<>();
        Date date = new Date();
        String userName = userItem.getName();
        // 获取业务流定义的名称
        String flowDefinitionName = flowDefinition.getName();

        // 创建一个新的业务流实例对象
        FlowInstance flowInstance = new FlowInstance();
        // 生成业务流实例的唯一ID
        String flowInstId = com.sdata.core.id.IdWorker.nextId();
        flowInstance.setId(flowInstId);
        flowInstance.setContent_id(dataId);
        flowInstance.setName(userName + "发起了" + flowDefinitionName);
        flowInstance.setName_en(userName + " initiated " + flowDefinitionName);
        flowInstance.setFlow_id(flowId);
        flowInstance.setCreate_member(userItem.getId());
        flowInstance.setCreate_time(date);
        flowInstance.setLast_modifier(userItem.getId());
        flowInstance.setLast_modify_time(date);
        flowInstance.setActiviti_type("flow");
        // 设置业务流实例的当前处理状态为“处理中”
        flowInstance.setProcess_status(FlowConstants.FlowInstProcessStatus.PROCESSING);
        formContentData.put(MetadataConstants.FormMandatoryField.FLOW_INSTANCE_ID, flowInstance.getId());

        // 将业务流实例的处理状态添加到需要更新到表单内容的数据中
        // 根据当前语言环境设置中文或英文状态描述
        formContentData.put(MetadataConstants.FormMandatoryField.PROCESS_STATUS,
                CookieUtils.isEn() ? FlowConstants.FlowInstProcessStatus.PROCESSING : FlowConstants.FlowInstProcessStatus.PROCESSING_CN);

        // 将构建好的业务流实例记录插入数据库
        flowInstanceServiceV2.insert(flowInstance);

        // 从 ThreadLocal 缓存中获取之前存储的 HttpServletRequest 对象
        HttpServletRequest request = (HttpServletRequest) ThreadLocalCache.getCache("request");
        // 将业务流名称添加到需要更新到表单内容的数据中
        formContentData.put(MetadataConstants.FormMandatoryField.FLOW_NAME, flowDefinitionName);
        // 将业务流实例名称添加到需要更新到表单内容的数据中 (根据语言环境)
        formContentData.put(MetadataConstants.FormMandatoryField.FLOW_INSTANCE_NAME, CookieUtils.isEn() ? flowInstance.getName_en() : flowInstance.getName());

        // 调用方法更新表单内容数据，将业务流相关信息写入表单记录
        // 第一次更新，主要写入流程实例ID、名称、状态等
        this.updateFormContentData(flowInstId, flowId, formContentData, dataId, formId, request);

        //2、业务流实例相关信息放入变量表
        Map<String, Object> flowContext = new HashMap<>();
        // 将表单数据放入上下文
        flowContext.put(FlowConstants.FlowVariable.FORM_DATA_ID, dataId);
        flowContext.put(FlowConstants.FlowVariable.FLOW_ID, flowId);
        flowContext.put(FlowConstants.FlowVariable.FLOW_NAME, flowDefinitionName);
        flowContext.put(FlowConstants.FlowVariable.FLOW_INST_ID, flowInstId);
        flowContext.put(FlowConstants.FlowVariable.FLOW_INST_NAME, CookieUtils.isEn() ? flowInstance.getName_en() : flowInstance.getName());
        flowContext.put(FlowConstants.FlowVariable.CREATE_TIME, date.getTime());
        flowContext.put(FlowConstants.FlowVariable.CREATE_MEMBER, userItem.getId());
        this.loadFlowContext(flowDefinition, flowInstance, dataId, date, flowContext, new ArrayList<>(), userItem.getId());

        // 启动activiti流程
        String processInstanceId = activitiFlowService.start(flowDefinition, flowContext, MetadataConstants.ActivitiType.FLOW, userItem.getId());
        // 获取业务流定义中的起始组件
        FlowComponent startFlowComponent = FlowUtilV2.getStartComponent(flowDefinition);
        // 查询业务流定义下的所有组件项
        List<FlowComponentItem> flowComponentItemList = flowComponentItemService.queryByFlowId(flowDefinition.getId());
        // 从组件项列表中找到类型为“发起”的组件项
        FlowComponentItem applyFlowComponentItem = flowComponentItemList.stream().filter(item -> FlowConstants.ComponentItemType.APPLY.equals(item.getType())).findFirst().orElse(null);
        // 如果没有找到“发起”类型的组件项，则直接返回 (流程可能无法正常启动或记录日志)
        if (Objects.isNull(applyFlowComponentItem)) {
            return;
        }
        // 添加一条发起日志
        FlowInstanceApprovalLog flowInstanceApprovalLog = new FlowInstanceApprovalLog();
        flowInstanceApprovalLog.setId(com.sdata.core.id.IdWorker.nextId())
                .setFlow_inst_id(flowInstance.getId())
                .setFlow_component_id(startFlowComponent.getId())
                .setFlow_component_item_id(applyFlowComponentItem.getId())
                .setCreate_time(date)
                .setCreate_member(userItem.getId());

        // 将日志记录插入数据库
        flowInstanceApprovalLogDaoV2.insert(flowInstanceApprovalLog);
        log.info("更新发起:{}", flowDefinition.getId());
        // 创建一个 FlowInstance 对象用于更新数据库记录
        FlowInstance updateFlowInst = new FlowInstance();
        updateFlowInst.setId(flowInstance.getId());

        // 将 Activiti 流程实例的ID更新到业务流实例记录中
        updateFlowInst.setActiviti_inst_id(processInstanceId);
        flowInstanceServiceV2.update(updateFlowInst);

        // 更新经办人
        this.updateFormContentData(flowInstance.getId(), flowId, new HashMap<>(), dataId, formId, request);
    }

    /**
     * 更新表单内容数据，主要用于同步业务流状态、经办人等信息到表单记录中
     *
     * @param flowInstId 业务流实例ID
     * @param flowId     业务流定义ID
     * @param data       需要更新到表单内容的数据 Map (会在此方法内添加经办人、业务状态等信息)
     * @param dataId     表单数据ID
     * @param formId     表单ID
     * @param request    HttpServletRequest 对象
     */
    private void updateFormContentData(String flowInstId, String flowId, Map<String, Object> data, String dataId, String formId, HttpServletRequest request) {
        // 查询经办人
        List<String> handleUserNames = flowInstanceTaskServiceV2.queryHandleUserNamesByFlowInstId(flowId, flowInstId);
        if (CollectionUtils.isNotEmpty(handleUserNames)) {
            handleUserNames = handleUserNames.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
        }
        data.put(MetadataConstants.FormMandatoryField.HANDLE_USERNAMES, String.join(",", handleUserNames));
        // 查询业务状态
        List<String> businessStatus = flowInstanceTaskServiceV2.queryBusinessStatusByFlowInstId(flowId, flowInstId);
        if (!data.containsKey(MetadataConstants.FormMandatoryField.BUSINESS_STATUS) && CollectionUtils.isNotEmpty(businessStatus)) {
            // 避免重复状态
            Set<String> uniqueStatuses = new HashSet<>(businessStatus);
            data.put(MetadataConstants.FormMandatoryField.BUSINESS_STATUS, String.join(",", uniqueStatuses));
        }

        // 最后修改人和最后修改人IP地址
        data.put(ComponentConstants.SystemComponentType.LAST_MODIFIER, FlowUtilV2.getCurrentUserId());
        data.put(ComponentConstants.SystemComponentType.LAST_MODIFIER_IP_ADDRESS, null);

        data.put(MetadataConstants.FormMandatoryField.ID, dataId);
        data.put("formId", formId);

        // 调用表单适配服务，执行实际的表单内容数据更新操作
        formAdaptationV2.updateFormContentData(data, request);
    }



    /**
     * 加载业务流上下文变量
     * 将表单数据、自动编号组件数据和系统变量加载到业务流上下文 Map 中
     *
     * @param flowDefinition  业务流定义对象
     * @param flowInstance    业务流实例对象
     * @param contentId       表单数据ID
     * @param date            当前时间
     * @param flowContext     业务流上下文 Map (待填充)
     * @param dataColumnsTemp 临时的表单数据列列表 (在此方法中未被使用，实际数据通过查询获取)
     * @param currentUserId   当前用户ID
     */
    private void loadFlowContext(FlowDefinition flowDefinition, FlowInstance flowInstance, String contentId, Date date, Map<String, Object> flowContext, List<Column> dataColumnsTemp, String currentUserId) {
        // 将核心业务流/表单信息再次放入上下文 (确保完整性)
        flowContext.put(FlowConstants.FlowVariable.FORM_DATA_ID, contentId);
        flowContext.put(FlowConstants.FlowVariable.FLOW_ID, flowDefinition.getId());
        flowContext.put(FlowConstants.FlowVariable.FLOW_NAME, flowDefinition.getName());
        flowContext.put(FlowConstants.FlowVariable.FLOW_INST_ID, flowInstance.getId());
        flowContext.put(FlowConstants.FlowVariable.FLOW_INST_NAME, CookieUtils.isEn() ? flowInstance.getName_en() : flowInstance.getName());
        flowContext.put(FlowConstants.FlowVariable.CREATE_TIME, date.getTime());
        flowContext.put(FlowConstants.FlowVariable.FORM_ID, flowDefinition.getForm_id());
        // 初始化一个空的 Map 用于存储业务流自定义变量
        flowContext.put(FlowConstants.FlowVariable.FLOW_VARIABLE, new HashMap<String, Object>());

        // 查询表单所有组件
        List<Component> componentList = formAdaptationV2.queryComponentByModelId(flowDefinition.getForm_id());
        List<String> componentIdList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(componentList)) {
            componentIdList = componentList.stream().map(Component::getId).collect(Collectors.toList());
        }

        // 1、将表单数据放入业务流变量上下文
        if (null != dataColumnsTemp) {
            flowInstanceServiceV2.formDataAddFlowContext(dataColumnsTemp, flowDefinition, flowContext, componentIdList);
        }
        // 2、判断是否是自动编号组件，自动编号组件的数据是后台生成的，所以自动编号组件的数据需要通过后台查询
        List<Component> autoCodeComponentList = formAdaptationV2.queryByModelIdAndShowType(flowDefinition.getForm_id(), MetadataConstants.FormShowType.AUTO_CODE);
        if (CollectionUtils.isNotEmpty(autoCodeComponentList)) {
            flowInstanceServiceV2.isAutoCodeComponent(autoCodeComponentList, flowDefinition, flowContext, flowInstance);
        }
        List<FlowComponentItem> flowComponentItemList = flowComponentItemService.queryByFlowId(flowDefinition.getId());
        FlowComponentItem applyFlowComponentItem = flowComponentItemList.stream().filter(item -> FlowConstants.ComponentItemType.APPLY.equals(item.getType())).findFirst().orElse(null);
        if (Objects.isNull(applyFlowComponentItem)) {
            return;
        }
        // 启动activiti流程
        if (StringUtils.isNotBlank(applyFlowComponentItem.getId())) {
            flowContext.put(FlowConstants.FlowVariable.DEFAULT_START_EVENT_ID + "_" + FlowConstants.FLOW_ACTIVITI_PREFIX + FlowUtilV2.convertId(applyFlowComponentItem.getId()) + "_num", 1);
        }

        // 调用方法将系统变量添加到业务流上下文中
        this.putFlowContextSysVariables(flowContext, userItemService.getById(currentUserId));
    }


    /**
     * 将系统变量写入业务流上下文
     *
     * @param flowContext 业务流上下文 Map (待填充系统变量)
     * @param userItem    当前用户的信息
     */
    // 写入系统变量
    private void putFlowContextSysVariables(Map<String, Object> flowContext, UserItem userItem) {
        // 获取系统变量 Map
        Map<String, Object> sysVariables = this.getSystemVariables(userItem);
        if (CollUtil.isNotEmpty(sysVariables)) {
            flowContext.putAll(sysVariables);
        }
    }


    /**
     * 获取系统变量 Map
     * 根据预定义的系统变量列表和当前用户信息构建系统变量 Map
     *
     * @param userItem 当前用户的信息
     * @return 包含系统变量的 Map
     */
    private Map<String, Object> getSystemVariables(UserItem userItem) {
        // 创建一个 Map 用于存储系统变量
        Map<String, Object> sysVariables = new HashMap<>();

        // 遍历预定义的系统变量名称列表 (FlowELUtil.FLOW_SYS_VARIABLES)
        for (String sysVariable : FlowELUtil.FLOW_SYS_VARIABLES) {
            sysVariables.put(FlowUtilV2.convSysVariableToFlowStyle(sysVariable), DatasetUtils.getCompareObj(sysVariable, userItem));
        }

        return sysVariables;
    }
}
